
/*
 * (a)  (C) 1990 by Adobe Systems Incorporated. All rights reserved.
 *
 * (b)  If this Sample Code is distributed as part of the Display PostScript
 *	System Software Development Kit from Adobe Systems Incorporated,
 *	then this copy is designated as Development Software and its use is
 *	subject to the terms of the License Agreement attached to such Kit.
 *
 * (c)  If this Sample Code is distributed independently, then the following
 *	terms apply:
 *
 * (d)  This file may be freely copied and redistributed as long as:
 *	1) Parts (a), (d), (e) and (f) continue to be included in the file,
 *	2) If the file has been modified in any way, a notice of such
 *      modification is conspicuously indicated.
 *
 * (e)  PostScript, Display PostScript, and Adobe are registered trademarks of
 *	Adobe Systems Incorporated.
 * 
 * (f) THE INFORMATION BELOW IS FURNISHED AS IS, IS SUBJECT TO
 *	CHANGE WITHOUT NOTICE, AND SHOULD NOT BE CONSTRUED
 *	AS A COMMITMENT BY ADOBE SYSTEMS INCORPORATED.
 *	ADOBE SYSTEMS INCORPORATED ASSUMES NO RESPONSIBILITY
 *	OR LIABILITY FOR ANY ERRORS OR INACCURACIES, MAKES NO
 *	WARRANTY OF ANY KIND (EXPRESS, IMPLIED OR STATUTORY)
 *	WITH RESPECT TO THIS INFORMATION, AND EXPRESSLY
 *	DISCLAIMS ANY AND ALL WARRANTIES OF MERCHANTABILITY, 
 *	FITNESS FOR PARTICULAR PURPOSES AND NONINFRINGEMENT
 *	OF THIRD PARTY RIGHTS.
 */

/*
 *	ImportApp.m
 *
 *	This class performs some of the global functions necessary to
 *	manage the application.
 *
 *	Several application wide resources are retained as
 *	instance variables. Examples are the hitPoint and
 *	upathBuffer buffers and the hitSetting value.
 *
 *	Version:	2.0
 *	Author:	Ken Fromm
 */

#import "ImportApp.h"
#import "Document.h"
#import "DrawingView.h"
#import "ImportPanel.h"
#import "Preferences.h"
#import "SaveAsPanel.h"

#import "DrawingViewWraps.h"

#import <appkit/NXCursor.h>
#import <appkit/NXImage.h>
#import <appkit/Matrix.h>
#import <appkit/Menu.h>
#import <appkit/MenuCell.h>
#import <appkit/defaults.h>
#import <appkit/nextstd.h>

#import <objc/List.h>
#import <sys/param.h>
#import <string.h>

struct  cursorRecord {
 	char		*name;
	NXPoint	hotspot;
};

const static struct cursorRecord cursors[] = {
	{NULL, 0.0, 0.0},				/* Select */
	{"cursorZoomUp.tiff", 8.0, 8.0},
	{"cursorRotate1.tiff", 8.0, 8.0},
	{"cursorZoomDown.tiff", 8.0, 8.0},
	{"cursorRotate2.tiff", 6.0, 4.0},
	{"cursorImport.tiff", 2.0, 1.0},
	{NULL, 0.0, 0.0}				/* Place */
};

char	ControlFont[ ] = "ControlPointsFont";

@implementation ImportApp

/*
 * Sets the updateAction for every menu item which sends to the
 * First Responder (i.e. their target is nil).  When autoupdate is on,
 * every event will be followed by an update of each of the menu items
 * which is visible.  This keep all unavailable menu items dimmed out
 * so that the user knows what options are available at any given time.
 * Returns the activate menu if is found in this menu.
 */ 
static void initMenu(id menu)
{
	int 			count;
	id 			matrix, cell;
	id			matrixTarget, cellTarget;

	matrix = [menu itemList];
	matrixTarget = [matrix target];

	count = [matrix cellCount];
	while (count--)
	{
		cell = [matrix cellAt:count :0];
		cellTarget = [cell target];
		if (!matrixTarget && !cellTarget)
			[cell setUpdateAction:@selector(menuItemUpdate:) forMenu:menu];
		else if ([cell hasSubmenu])
			initMenu(cellTarget);
	}
}

/*
 *	Checks to see if the passed window's delegate is a Document
 *	instance. If it is, it returns that document, otherwise it returns nil.
 */
static id documentInWindow(id window)
{
	id 		document = [window delegate];
    
	return [document  isKindOf:[Document class]] ? document : nil;
}

/*
 *	Searches the window list looking for a Document with the specified
 *	name. Returns the window containing the document if found.
 *	If name == NULL then the first document found is returned.
 */
static id findDocument(const char *name)
{
	int 		count;
	id 		document, window, windowList;

	windowList = [NXApp windowList];
	count = [windowList count];
	while (count--)
	{
		window = [windowList objectAt:count];
		document = documentInWindow(window);
	
		if (document && (!name || !strcmp([document filename], name))) 
			return window;
	}

	return nil;
}

/*
 *	Opens a file with the given name in the specified directory.
 *	If we already have that file open, it is ordered front.
 *	Returns the document if successful, nil otherwise.
 */
static id openFile(const char *directory, const char *name, BOOL display)
{
	id		window;

	char		buffer[MAXPATHLEN+1], path[MAXPATHLEN+1];

	if (name && *name)
	{
		if (!directory)
		{
			directory = ".";
		} 
		else if (*directory != '/')
		{
			strcpy(buffer, "./");
			strcat(buffer, directory);
			directory = buffer;
		}
	
		if (!chdir(directory) && getwd(path))
		{
			strcat(path, "/");
			strcat(path, name);
			window = findDocument(path);
			if (window)
			{
				if (display)
					[window makeKeyAndOrderFront:window];
				return [window delegate];
			} 
			else
				return [Document newFromFile:path];
		} 
		else 
			NXRunAlertPanel("Open", "Invalid path: %s", "OK", NULL, NULL, directory);
	}

	return nil;
}

/*
* 	Allocate a buffer for use by any drawing view to send
* 	data to the PostScript server.
*
*	Allocate this buffer for use by any drawing view to
*	use as the hitdetection userpath. Initialize the description
*	since most of the components will remain the same.
*/
+ new
{
	int		i;

	self = [super new];

	[self setAutoupdate:YES];

	PSWDefineFont(ControlFont);
	hitSetting = 4.0;
	contextFlag = imagingFlag = YES;

	NX_MALLOC(upathBuffer.pts, float, PTS_BUFFER);
	NX_MALLOC(upathBuffer.ops, char, OPS_BUFFER);

	NX_MALLOC(hitPoint.pts, float, PTS_HITPOINT);
	NX_MALLOC(hitPoint.ops, char, OPS_HITPOINT);

	for (i = 0; i < PTS_HITPOINT; i++)
		hitPoint.pts[i] = 0;			
	hitPoint.num_pts = i;
 	
	i = 0;
	hitPoint.ops[i++] = dps_setbbox;
	hitPoint.ops[i++] = dps_moveto;
	hitPoint.ops[i++] = dps_rlineto;
	hitPoint.ops[i++] = dps_rlineto;
	hitPoint.ops[i++] = dps_rlineto;
	hitPoint.ops[i++] = dps_closepath;
	hitPoint.num_ops = i;

	return self;
}

/*  Initializes the defaults. */
+ initialize
{
	const NXDefaultsVector EpsfDefaults = {
		{ "Quit", NULL },
		{ NULL, NULL }
	};

	NXRegisterDefaults("Epsf", EpsfDefaults);

	return self;
}

- free
{
	if (upathBuffer.pts)
		NX_FREE(upathBuffer.pts);
	if (upathBuffer.ops)
		NX_FREE(upathBuffer.ops);
	if (hitPoint.pts)
		NX_FREE(hitPoint.pts);
	if (hitPoint.ops)
		NX_FREE(hitPoint.ops);

	return [super free];
}

- setToolPanel:anObject
{
	toolpanelId = anObject;

	return self;
}

- setToolMatrix:anObject
{
	toolmatrixId = anObject;

	return self;
}

- setSaveAccessory:anObject
{    
	[[SaveAsPanel new]  setAccessoryView:anObject];

	return self;
}

- setImportAccessory:anObject
{    
	[[ImportPanel  new]  setImportAccessory:anObject];

	return self;
}

/* Calculate the position for the next window. */
- getPosition:(NXPoint *)location  forSize:(NXSize *)winSize
{
	int		col, row;
	
	col = (numWindows/NUMPERCOL) % NUMCOLUMNS;
	row = numWindows % NUMPERCOL;
	
	location->x = WINDOWSTARTX + (col * OFFSETLEFT) + (row * OFFSETX);
	location->y = WINDOWSTARTY - winSize->height - (row * OFFSETY);

	numWindows++;

	return self;
}

/*
*	Teaching aids
*/
- setContextFlag:sender
{
	if (!contextFlag)
		[[sender selectedCell] setTitle:"Separate Context On"];
	else
		[[sender selectedCell] setTitle:"Separate Context Off"];

	contextFlag = !contextFlag;
	
	return self;
}

- (BOOL) contextFlag
{
	return contextFlag;
}

- setShowEpsfFlag:sender
{
	if (!showEpsfFlag)
		[[sender selectedCell] setTitle:"Show Epsf On"];
	else
		[[sender selectedCell] setTitle:"Show Epsf Off"];

	showEpsfFlag = !showEpsfFlag;

	return self;
}

- (BOOL) showEpsfFlag
{
	return showEpsfFlag;
}

- setShowBufferFlag:sender
{
	if (!showBufferFlag)
		[[sender selectedCell] setTitle:"Show Buffer On"];
	else
		[[sender selectedCell] setTitle:"Show Buffer Off"];

	showBufferFlag = !showBufferFlag;

	if (showBufferFlag)
		[[[self  currentDocument]  drawingView]  showBuffer:self];
	else
		[[[self  currentDocument]  drawingView]  hideBuffer:self];

	return self;
}

- (BOOL) showBufferFlag
{
	return showBufferFlag;
}

/*	Change the menu title and toggle the trace boolean.    */
- setTracingFlag:sender
{
	if (!tracingFlag)
		[[sender selectedCell] setTitle:"Trace On"];
	else
		[[sender selectedCell] setTitle:"Trace Off"];

	tracingFlag = !tracingFlag;
	
	return self;
}

- (BOOL) tracingFlag
{
	return tracingFlag;
}

- setImagingFlag:sender
{
	imagingFlag = [sender  state];
	[[[self  currentDocument]  drawingView]  display];

	return self;
}

- (BOOL) imagingFlag
{
	return imagingFlag;
}

- setHitSetting:sender
{
 	hitSetting = [sender  floatValue];

	return self;
}

- (float) hitSetting
{
	return hitSetting;
}

- (UPath *) hitPoint;
{
	return &hitPoint;
}

- (UPath *) upathBuffer;
{
	return &upathBuffer;
}

- currentDocument
{
	return documentInWindow(mainWindow);
}

- (const char *)currentDirectory
{
	const char *retval = [[self  currentDocument] directory];

	if (!retval || !*retval)
		retval = [[OpenPanel new] directory];

	return retval;
}

/* Open a new document. */
- new:sender
{
	[Document new];

	return self;
}

/*
 * Called by pressing Open... in the Window menu.
 */
- open:sender
{
	id			openpanel;

	char			path[MAXPATHLEN+1];

	const char		*directory;

	const char *const		*files;

	static const char *const		filetype[2] = {"adb", NULL};

	directory = NULL;
	directory = [[self  currentDocument]  directory];
	if (!directory)
		if (getwd(path))
			directory = path;
 	openpanel = [[ImportPanel  new]  setOpen];
     	if ([openpanel  runModalForDirectory:directory  file:NULL  types:filetype])
	{
		files = [openpanel filenames];
		directory = [openpanel directory];
		while (files && *files)
		{
			openFile(directory, *files, YES);
			files++;
		}
	}

	return self;
}

/*
 *	Sends a windowWillClose message to all open Documents
 *	before the program terminates.
 */
- terminate:sender
{
	int		count;
	id		window, document;

	count = [windowList count];
	while (count--)
	{
		window = [windowList  objectAt:count];
		document = documentInWindow(window);
		if (document)
		{
			if ([document  windowWillClose:window])
				[window close];
			else
				return self;
		}
	}

	return [super terminate:sender];
}

- setTool:sender
{
	[self  setOperation:[sender  selectedTag]];

	return self;
}

- resourcePanel
{
	if (!resourcePanel)
		[NXApp  loadNibSection:"ResourcePanel.nib"  owner:self  withNames:NO];

	return resourcePanel;
}

/*
*	Return the cursor for the appropriate index. Create
*	one if non-existent.
*/
- getCursor:(int) index
{
	id		imageId;

	if (!cursorIds[index])
	{
		if (cursors[index].name)
		{
			imageId = [NXImage  newFromSection:cursors[index].name];
			cursorIds[index]= [NXCursor  newFromImage:imageId];
			[cursorIds[index]  setHotSpot:&cursors[index].hotspot];
		}
	}

	return cursorIds[index];
}
	
- cursor
{
	id		imageId;

	if (!cursorIds[operation])
	{
		if (cursors[operation].name)
		{
			imageId = [NXImage  newFromSection:cursors[operation].name];
			cursorIds[operation]= [NXCursor  newFromImage:imageId];
			[cursorIds[operation]  setHotSpot:&cursors[operation].hotspot];
		}
		else
			cursorIds[operation] = NXArrow;
	}

	return cursorIds[operation];
}

- (int) operation
{   	
	return operation;
}

- setOperation:(int) newOperation
{
	id		currentDoc;

	if (newOperation < OP_FIRST || newOperation > OP_LAST)
		newOperation = OP_SELECT;
	operation = newOperation;

	currentDoc = [self  currentDocument];
	[[currentDoc  window]  invalidateCursorRectsForView:[currentDoc drawingView]];

	return self;
}

- clearOperation
{
	[self  setOperation:OP_SELECT];
	[toolmatrixId  selectCellWithTag:OP_SELECT];

	return self;
}

/*
 * Application object delegate methods.
 * Messages that would be sent to the delegate are sent to the Application
 * object or a subclass if no delagate exists. 
 */

/*
 * Initialize the menus.
 * Sets the tool palette so it never becomes the key window.
 * Check for files to open specified on the command line.
 * If there are no open documents, then open a blank one.
 */
- appDidInit:sender
{
	id			docId;
	int			i;
	char			buffer[MAXPATHLEN+1];
	char			*directory, *name, *ext;

	[toolpanelId  removeFromEventMask:NX_KEYDOWNMASK|NX_KEYUPMASK];
	[toolpanelId  orderFront:self];

	initMenu([NXApp mainMenu]);

	if (NXArgc > 1)
	{
		for (i = 1; i < NXArgc; i++)
		{
			strcpy(buffer, NXArgv[i]);
			ext = strrchr(buffer, '.');
			if (!ext || strcmp(ext, ".adb"))
				strcat(buffer, ".adb");
	
			name = strrchr(buffer, '/');

			if (name)
			{
				*name++ = '\0';
				directory = buffer;
			} 
			else
			{
				name = buffer;
				directory = NULL;
			}
			docId = openFile(directory, name, YES);
		}
	}
    
	/* if none opened, open one */
	if (!findDocument(NULL))
		[self new:self];	
	
	if (NXGetDefaultValue([self appName], "Quit"))
	{
		[self activateSelf:YES];
		NXPing();
		[self terminate:self];
	}

	return self;
}

/*
 * This method is performed whenever a user double-clicks on an icon
 * in the Workspace Manager representing an Epsf document.
 */
- (int)appOpenFile:(const char *)path type:(const char *)type
{
	char		*name;
	char		directory[MAXPATHLEN+1];

	if (type && !strcmp(type, "adb"))
	{
		strcpy(directory, path);
		name = strrchr(directory, '/');
		if (name)
		{
			*name++ = '\0';
			return (openFile(directory, name, YES) ? YES : NO);
		}
	}

	return NO;
}

/*  We accept any number of appOpenFile:type: messages. */
- (BOOL)appAcceptsAnotherFile:sender
{
	return YES;
}

/* Automatic update methods */

/*
 * Method called by all menu items which send their actions to the
 * First Responder.  First, if the object which would respond were the
 * action sent down the responder chain also responds to the message
 * validateCommand:, then it is sent validateCommand: to determine
 * whether that command is valid now, otherwise, if there is a responder
 * to the message, then it is assumed that the item is valid.
 * The method returns YES if the cell has changed its appearance (so that
 * the caller (a Menu) knows to redraw it).
 */
- (BOOL)menuItemUpdate:menuCell
{
	SEL			action;
	id			responder, target;
	BOOL		enable;

	target = [menuCell  target];
	enable = [menuCell  isEnabled];

	if (!target)
	{
		action = [menuCell action];
		responder = [self calcTargetForAction:action];
	
		if ([responder respondsTo:@selector(validateCommand:)])
			enable = [responder validateCommand:menuCell];
		else
			enable = responder ? YES : NO;
	}

	if ([menuCell isEnabled] != enable)
	{
		[menuCell setEnabled:enable];
		return YES;
	}
	else
		return NO;
}

@end

